<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            list-style: none;
        }

        .box {
            width: 1000px;
            height: 800px;
            margin: 30px auto;
            display: flex;
            border: 1px solid;
        }

        #myCanvas {
            margin: auto;
            border: 1px solid;
        }
    </style>
</head>

<body>

    <div class="box">
        <canvas id="myCanvas" height="750" width="950" tabindex="333"></canvas>
    </div>
</body>
<script>
    // 画普通箭头
    function drawCommonArrow(ctx, arrowParam) {
        // 确定上箭柄位置
        let topArrPointX = arrowParam.endPoint.x - arrowParam.arrowLength * Math.sin(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.startPoint.y), (arrowParam.endPoint.x - arrowParam.startPoint.x)))
        let topArrPointY = arrowParam.endPoint.y - arrowParam.arrowLength * Math.cos(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.startPoint.y), (arrowParam.endPoint.x - arrowParam.startPoint.x)))
        let topArrPoint = {
            x: topArrPointX,
            y: topArrPointY
        }

        // 确定下箭柄位置
        let botArrPointX = arrowParam.endPoint.x - arrowParam.arrowLength * Math.cos(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.startPoint.y), (arrowParam.endPoint.x - arrowParam.startPoint.x)))
        let botArrPointY = arrowParam.endPoint.y + arrowParam.arrowLength * Math.sin(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.startPoint.y), (arrowParam.endPoint.x - arrowParam.startPoint.x)))
        let botArrPoint = {
            x: botArrPointX,
            y: botArrPointY
        }

        ctx.save();
        ctx.beginPath();
        // 原本位置的箭头
        drawLine(ctx, arrowParam.endPoint, topArrPoint)  // 画上箭头柄
        drawLine(ctx, arrowParam.endPoint, botArrPoint)  // 画下箭头柄
        drawLine(ctx, arrowParam.startPoint, arrowParam.endPoint)    // 画箭柄
        ctx.closePath();
        ctx.restore();

    }
</script>
<script>
    /** @type { HTMLCanvasElement } */
    const myCanvas = document.getElementById('myCanvas');
    const ctx = myCanvas.getContext('2d');
    // 设计箭头样式参数 ->
    let arrowParams = {
        startPoint: {
            x: 400,
            y: 436
        },
        endPoint: {
            x: 400,
            y: 650
        },
        arrowLength: 55,    // 箭头小边长
        turnPoint: [
            {
                x: 123,
                y: 36
            },
            {
                x: 223,
                y: 360
            },
            {
                x: 123,
                y: 410
            },
            {
                x: 223,
                y: 470
            }
        ],  // 旋转原点
        txt: '',    // 箭头上的文字
        color: '',  // 箭头颜色
        style: '' // 箭柄样式，比如虚线：dashed
    }
    /**
     * @description: 画可旋转的箭头：第一种画箭头方式，已知头尾求箭头整体。
     * @param {*} ctx   canvas 的 context 对象
     * @param {*} arrowParam    箭头 JSON 对象体
     * @param {number} rotateDegree  旋转角度
     * @param {string} lineStyle
     * @return { undefined }
     */
    function drawRotateArrow1(ctx, arrowParam, rotateDegree = 0) {
        let originPoint = {
            x: 0,
            y: 0
        }
        // 确定上箭柄位置
        let topArrPointX, topArrPointY, topArrPoint;
        // 确定下箭柄位置
        let botArrPointX, botArrPointY, botArrPoint;

        if (arrowParam.turnPoint && arrowParam.turnPoint.length) {
            // 如果有拐点
            topArrPointX = arrowParam.endPoint.x - arrowParam.arrowLength * Math.sin(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.turnPoint[arrowParam.turnPoint.length - 1].y), (arrowParam.endPoint.x - arrowParam.turnPoint[arrowParam.turnPoint.length - 1].x)))
            topArrPointY = arrowParam.endPoint.y - arrowParam.arrowLength * Math.cos(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.turnPoint[arrowParam.turnPoint.length - 1].y), (arrowParam.endPoint.x - arrowParam.turnPoint[arrowParam.turnPoint.length - 1].x)))
            topArrPoint = {
                x: topArrPointX,
                y: topArrPointY
            }

            botArrPointX = arrowParam.endPoint.x - arrowParam.arrowLength * Math.cos(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.turnPoint[arrowParam.turnPoint.length - 1].y), (arrowParam.endPoint.x - arrowParam.turnPoint[arrowParam.turnPoint.length - 1].x)))
            botArrPointY = arrowParam.endPoint.y + arrowParam.arrowLength * Math.sin(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.turnPoint[arrowParam.turnPoint.length - 1].y), (arrowParam.endPoint.x - arrowParam.turnPoint[arrowParam.turnPoint.length - 1].x)))
            botArrPoint = {
                x: botArrPointX,
                y: botArrPointY
            }
        } else {
            // 如果没有拐点
            topArrPointX = arrowParam.endPoint.x - arrowParam.arrowLength * Math.sin(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.startPoint.y), (arrowParam.endPoint.x - arrowParam.startPoint.x)))
            topArrPointY = arrowParam.endPoint.y - arrowParam.arrowLength * Math.cos(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.startPoint.y), (arrowParam.endPoint.x - arrowParam.startPoint.x)))
            topArrPoint = {
                x: topArrPointX,
                y: topArrPointY
            }

            botArrPointX = arrowParam.endPoint.x - arrowParam.arrowLength * Math.cos(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.startPoint.y), (arrowParam.endPoint.x - arrowParam.startPoint.x)))
            botArrPointY = arrowParam.endPoint.y + arrowParam.arrowLength * Math.sin(Math.PI / 4 - Math.atan2((arrowParam.endPoint.y - arrowParam.startPoint.y), (arrowParam.endPoint.x - arrowParam.startPoint.x)))
            botArrPoint = {
                x: botArrPointX,
                y: botArrPointY
            }
        }

        ctx.save()
        ctx.beginPath()
        ctx.translate(arrowParam.startPoint.x, arrowParam.startPoint.y)
        ctx.rotate(Math.PI / 180 * rotateDegree)
        if (arrowParam.turnPoint && arrowParam.turnPoint.length) {
            // 如果由转折点的话
            arrowParam.turnPoint.forEach((item, idx) => {
                if (idx === 0) {
                    // 第一个转折点
                    drawLine(ctx, originPoint, {
                        x: item.x - arrowParam.startPoint.x,
                        y: item.y - arrowParam.startPoint.y,
                    })
                } else {
                    // 最后一个转折点
                    drawLine(ctx, {
                        x: item.x - arrowParam.startPoint.x,
                        y: item.y - arrowParam.startPoint.y
                    }, {
                        x: arrowParam.turnPoint[idx - 1].x - arrowParam.startPoint.x,
                        y: arrowParam.turnPoint[idx - 1].y - arrowParam.startPoint.y
                    })
                }
            });
            drawLine(ctx, {
                x: arrowParam.turnPoint[arrowParam.turnPoint.length - 1].x - arrowParam.startPoint.x,
                y: arrowParam.turnPoint[arrowParam.turnPoint.length - 1].y - arrowParam.startPoint.y,
            }, {
                x: arrowParam.endPoint.x - arrowParam.startPoint.x,
                y: arrowParam.endPoint.y - arrowParam.startPoint.y,
            })
        } else {
            // 如果没有转折点
            drawLine(ctx, { x: 0, y: 0 }, {
                x: arrowParam.endPoint.x - arrowParam.startPoint.x,
                y: arrowParam.endPoint.y - arrowParam.startPoint.y
            })
        }
        // 画箭头
        drawLine(ctx, {
            x: arrowParam.endPoint.x - arrowParam.startPoint.x,
            y: arrowParam.endPoint.y - arrowParam.startPoint.y
        }, {
            x: topArrPoint.x - arrowParam.startPoint.x,
            y: topArrPoint.y - arrowParam.startPoint.y
        })
        drawLine(ctx, {
            x: arrowParam.endPoint.x - arrowParam.startPoint.x,
            y: arrowParam.endPoint.y - arrowParam.startPoint.y
        }, {
            x: botArrPoint.x - arrowParam.startPoint.x,
            y: botArrPoint.y - arrowParam.startPoint.y
        })
        ctx.closePath();
        ctx.restore();
    }

    // 画线函数
    function drawLine(ctx, startPoint, endPoint, lineStyle = '#000') {
        ctx.save();
        ctx.beginPath();

        ctx.moveTo(startPoint.x, startPoint.y)
        ctx.lineTo(endPoint.x, endPoint.y)
        ctx.strokeStyle = lineStyle
        ctx.stroke()

        ctx.closePath();
        ctx.restore();
    }

    ctx.save();
    ctx.beginPath();
    drawRotateArrow1(ctx, arrowParams)
    drawRotateArrow1(ctx, arrowParams, 10)
    // drawRotateArrow1(ctx, arrowParams, 20)
    drawRotateArrow1(ctx, arrowParams, 30)
    // drawRotateArrow1(ctx, arrowParams, 40)
    // drawRotateArrow1(ctx, arrowParams, 50)
    // drawRotateArrow1(ctx, arrowParams, 60)
    // drawRotateArrow1(ctx, arrowParams, 70)
    // drawRotateArrow1(ctx, arrowParams, 80)
    drawRotateArrow1(ctx, arrowParams, 90)
    drawLine(ctx, {
        x: 700,
        y: 700
    }, {
        x: 600,
        y: 500
    }, '#f0f')
    ctx.closePath();
    ctx.restore();

</script>

</html>